package com.nineclock.attendance.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.nineclock.attendance.dto.AttendGroupDTO;
import com.nineclock.attendance.dto.AttendPunchDTO;
import com.nineclock.attendance.dto.AttendPunchUserWholeDayDTO;
import com.nineclock.attendance.dto.AttendsDTO;
import com.nineclock.attendance.enums.AttendEnums;
import com.nineclock.attendance.mapper.AttendGroupMapper;
import com.nineclock.attendance.mapper.AttendPunchMapper;
import com.nineclock.attendance.pojo.AttendPunch;
import com.nineclock.attendance.service.AttendGroupService;
import com.nineclock.attendance.service.AttendPunchService;
import com.nineclock.attendance.utils.PunchUtils;
import com.nineclock.common.enums.ResponseEnum;
import com.nineclock.common.exception.NcException;
import com.nineclock.common.threadlocal.CurrentUserHolder;
import com.nineclock.common.utils.BeanHelper;
import com.nineclock.common.utils.DateTimeUtil;
import com.nineclock.system.dto.SysCompanyUserDTO;
import com.nineclock.system.feign.SysUserFeign;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 黑马程序员
 * @Company http://www.ithiema.com
 * @Version 1.0
 */
@Service
@Slf4j
public class AttendPunchServiceImpl implements AttendPunchService {


    @Autowired
    AttendPunchMapper attendPunchMapper;

    @Autowired
    AttendGroupMapper attendGroupMapper;

    @Autowired
    AttendGroupService attendGroupService;

    @Autowired
    SysUserFeign userFeign;

    /**
     * 打卡功能(新增)
     * @param attendPunchDTO
     */
    @Override
    public void punch(AttendPunchDTO attendPunchDTO) {
        //健壮性判断
        if(attendPunchDTO == null){
            throw new NcException(ResponseEnum.INVALID_PARAM_ERROR);
        }

        //查询对应的考勤组
        AttendGroupDTO attendGroup = attendGroupService.getAttendGroupByUserId();

        //复制 为考勤打卡对象
        AttendPunch attendPunch = BeanHelper.copyProperties(attendPunchDTO, AttendPunch.class);
        attendPunch.setLat(new BigDecimal(attendPunchDTO.getLat()));
        attendPunch.setLng(new BigDecimal(attendPunchDTO.getLng()));
        //设置打卡日期
        attendPunch.setPunchTime(new Date()); //打卡时间
        attendPunch.setPunchDateStr(DateUtil.format(new Date(), "yyyy-MM-dd")); //打卡日期
        attendPunch.setCreateTime(new Date());
        attendPunch.setCompanyId(CurrentUserHolder.get().getCompanyId());
        attendPunch.setCompanyUserId(CurrentUserHolder.get().getCompanyUserId());
        attendPunch.setPunchOnOffWork(DateTimeUtil.noonType(new Date()) + 1); //上下午打卡

        //1. 是否有效范围内打卡
        boolean validatePunchArea = PunchUtils.validatePunchArea(attendPunchDTO, attendGroup);
        if(!validatePunchArea){
            throw new NcException(ResponseEnum.PUNCH_INVALID_AREA);
        }
        attendPunch.setAreaValid(true);

        //2. 是否有效打卡
        boolean validatePunch = this.validatePunch(attendPunch);
        //如果是有效打卡, 设置打卡类型
        if(validatePunch){
            PunchUtils.setPunchType(attendPunch, attendGroup);
        }
        //3. 是否工作日打卡
        boolean validateWorkDay = PunchUtils.validateWorkDay(attendGroup);
        if(!validateWorkDay){
            throw new NcException(ResponseEnum.PUNCH_INVALID_DAY);
        }
        //工作日打卡
        attendPunch.setDayType(1);

        attendPunchMapper.insert(attendPunch);
    }

    @Override
    public AttendPunchUserWholeDayDTO queryPunchRecord() {
        //获取员工id
        Long companyUserId = CurrentUserHolder.get().getCompanyUserId();
        //获取当天日期字符串
        String dateStr = DateTimeUtil.dateToStr(new Date(), "yyyy-MM-dd");
        //包含一整天的打卡记录
        AttendPunchUserWholeDayDTO attendPunchUserWholeDayDTO = new AttendPunchUserWholeDayDTO();
        //获取上午打卡记录

        LambdaQueryWrapper<AttendPunch> attendPunchQw1 = new LambdaQueryWrapper<>();
        attendPunchQw1.eq(AttendPunch::getCompanyUserId, companyUserId);
        attendPunchQw1.eq(AttendPunch::getCompanyId, CurrentUserHolder.get().getCompanyId());
        attendPunchQw1.eq(AttendPunch::getEffectiveValid, true);
        attendPunchQw1.eq(AttendPunch::getPunchOnOffWork, 1);
        attendPunchQw1.eq(AttendPunch::getPunchDateStr, dateStr);
        AttendPunch attendPunch1 = attendPunchMapper.selectOne(attendPunchQw1);

        //获取下午打卡记录
        LambdaQueryWrapper<AttendPunch> attendPunchQw2 = new LambdaQueryWrapper<>();
        attendPunchQw2.eq(AttendPunch::getCompanyUserId, companyUserId);
        attendPunchQw2.eq(AttendPunch::getCompanyId, CurrentUserHolder.get().getCompanyId());
        attendPunchQw2.eq(AttendPunch::getEffectiveValid, true);
        attendPunchQw2.eq(AttendPunch::getPunchOnOffWork, 2);
        attendPunchQw2.eq(AttendPunch::getPunchDateStr, dateStr);
        AttendPunch attendPunch2 = attendPunchMapper.selectOne(attendPunchQw2);
        //组合
        if(attendPunch1 != null){
            attendPunchUserWholeDayDTO.setAttendPunchMorningResponse(BeanHelper.copyProperties(attendPunch1, AttendPunchDTO.class));
        }
        if(attendPunch2 != null){
            attendPunchUserWholeDayDTO.setAttendPunchAfterNoonResponse(BeanHelper.copyProperties(attendPunch2, AttendPunchDTO.class));
        }
        return attendPunchUserWholeDayDTO;
    }

    /**
     * 1). 获取起止日期内(包含起止日期), 所有日期的集合 ;

     2). 获取当前企业下所有的员工数据列表 ;

     3). 查询每个员工每天的打卡情况

     遍历企业员工列表:

     A. 封装用户基本信息

     B. 获取该用户的指定时间范围内的有效打卡数据

     C. 对打卡数据进行处理, 进行数据组装

     D. 返回结果

     * @param startTime
     * @param endTime
     * @return
     */
    @Override
    public List<AttendPunchDTO> queryMembers(String startTime, String endTime) {
        //获取日期范围内的日期列表
        List<Date> dateList = DateTimeUtil.getDatesBetweenTwoDate(startTime, endTime, "yyyy-MM-dd");
        //转换为日期字符串列表
        List<String> dateStrList = dateList.stream().map(date -> {
            String format = DateUtil.format(date, "yyyy-MM-dd");
            return format;
        }).collect(Collectors.toList());

        //获取当前企业下所有的员工列表
        List<SysCompanyUserDTO> companyUserDTOList = userFeign.queryAllCompanyUser().getData();

        List<AttendPunchDTO> resultList = this.getAttendPunchList(companyUserDTOList, startTime,endTime, dateStrList);

        return resultList;
    }

    public List<AttendPunchDTO> getAttendPunchList(List<SysCompanyUserDTO> companyUserDTOList, String startTime, String endTime, List<String> dateStrList  ){
        List<AttendPunchDTO> resultList = new ArrayList<>();
        //查询每个员工每天的打卡情况
        if(CollectionUtil.isNotEmpty(companyUserDTOList)){
            for (SysCompanyUserDTO companyUserDTO : companyUserDTOList) {
                //每一个员工 应该对应一个 打卡情况(复制了基本信息给 考勤 对象)
                AttendPunchDTO attendPunchDTO = BeanHelper.copyProperties(companyUserDTO, AttendPunchDTO.class);


                //查询每个员工的(指定时间范围内的) 考勤情况
                LambdaQueryWrapper<AttendPunch> attendPunchQw = new LambdaQueryWrapper<>();
                attendPunchQw.eq(AttendPunch::getCompanyId, companyUserDTO.getCompanyId()); //企业id
                attendPunchQw.eq(AttendPunch::getCompanyUserId, companyUserDTO.getId()); //员工id
                attendPunchQw.eq(AttendPunch::getEffectiveValid,true); //有效打卡
                attendPunchQw.between(AttendPunch::getPunchDateStr, startTime, endTime); //指定时间范围内
                //每个员工 指定时间 范围内 考勤列表( 每天 最多两个打卡)
                List<AttendPunch> attendPunchList = attendPunchMapper.selectList(attendPunchQw);
                // 按照日期对数据进行分组
                Map<String, List<AttendPunch>> map =
                        attendPunchList.stream().collect(Collectors.groupingBy(AttendPunch::getPunchDateStr));


                //创建每天打卡情况的集合
                List<AttendsDTO> list = new ArrayList<>();
                //封装每天的打卡情况
                //对日期列表进行循环
                for (String dateStr : dateStrList) {
                    AttendsDTO attendsDTO = new AttendsDTO();

                    //从map集合中获取 指定日期的 考勤列表
                    List<AttendPunch> attendPunches = map.get(dateStr);
                    attendsDTO.setAttendDate(dateStr);
                    attendsDTO.setAttendStatus(this.handleAttendStatus(attendPunches));

                    list.add(attendsDTO);
                }

                //设置员工的每天打卡情况
                attendPunchDTO.setAttendsList(list);

                resultList.add(attendPunchDTO);
            }
        }
        return resultList;
    }



    /**
     * 获取时间范围内的 考勤列表数据
     * @param startTime
     * @param endTime
     */
    @Override
    public void export(String startTime, String endTime) throws IOException {
        //获取数据
        List<AttendPunchDTO> attendPunchDTOList = this.queryMembers(startTime, endTime);


        //设置文件下载的头信息
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        response.setHeader("Content-disposition", "attachment;filename=Attendance.xlsx ");
        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setCharacterEncoding("utf-8");


        WriteCellStyle cellStyle = new WriteCellStyle();
        //表头和内容都是水平居中
        cellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);

        //设置字体
        WriteFont font = new WriteFont();
        font.setFontName("宋体");
        font.setFontHeightInPoints((short)14);
        cellStyle.setWriteFont(font);

        HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(cellStyle,cellStyle);


        EasyExcel
                .write(response.getOutputStream())
                .excelType(ExcelTypeEnum.XLSX)
                .head(header(attendPunchDTOList))
                .registerWriteHandler(styleStrategy)
                .registerWriteHandler(new MyColumnStyle())
                .sheet("员工列表")
                .doWrite(data(attendPunchDTOList));
    }

    /**
     *
     * @param companyId 企业id
     * @return excel文件的地址
     * @throws IOException
     */
    @Override
    public String generateExcelReportByCompanyId(Long companyId) throws IOException {

        //1. 获取当月日期集合
        String firstDay = DateTimeUtil.getCurrentMonthFirstDay();
        String lastDay = DateTimeUtil.getCurrentMonthLastDay();
        List<Date> dateList = DateTimeUtil.getDatesBetweenTwoDate(firstDay, lastDay, "yyyy-MM-dd");
        List<String> dateStrList = dateList.stream().map(date -> {
            return DateTimeUtil.dateToStr(date, "yyyy-MM-dd");
        }).collect(Collectors.toList());

        //2. 对企业的id进行遍历， 分别获取每个企业员工列表
        List<SysCompanyUserDTO> companyUserDTOList = userFeign.queryCompanyUser(companyId, null).getData();

        //获取考勤列表数据
        List<AttendPunchDTO> attendPunchList = this.getAttendPunchList(companyUserDTOList, firstDay, lastDay, dateStrList);



        WriteCellStyle cellStyle = new WriteCellStyle();
        //表头和内容都是水平居中
        cellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);

        //设置字体
        WriteFont font = new WriteFont();
        font.setFontName("宋体");
        font.setFontHeightInPoints((short)14);
        cellStyle.setWriteFont(font);

        HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(cellStyle,cellStyle);
        // 2021-06-01
        String date = firstDay.substring(0, 7);

        //如果路径不存在，则创建
        String path = "D://考勤数据/"+ date+"/";
        if(!new File(path).exists()){
            new File(path).mkdirs();
        }

        String excelpath = path + companyUserDTOList.get(0).getCompanyName()+"考勤.xls";
        EasyExcel
                .write(new File(excelpath))
                .excelType(ExcelTypeEnum.XLS)
                .head(header(attendPunchList))
                .registerWriteHandler(styleStrategy)
                .registerWriteHandler(new MyColumnStyle())
                .sheet("考勤数据")
                .doWrite(data(attendPunchList));
        return excelpath;
    }

    //封装数据
    private List<List<String>> data(List<AttendPunchDTO> attendPunchDTOList) {
        List<List<String>> data = new ArrayList<>();
        if(CollectionUtil.isNotEmpty(attendPunchDTOList)){
            //循环每一个员工的考勤对象, 每一个员工的考勤对象应该对应 一个list集合
            for (AttendPunchDTO attendPunchDTO : attendPunchDTOList) {
                //每一个员工的考勤情况
                List<String> list = new ArrayList<>();
                list.add(attendPunchDTO.getUserName());
                list.add(attendPunchDTO.getWorkNumber());
                list.add(attendPunchDTO.getDepartmentName());
                list.add(attendPunchDTO.getPost());
                // 该员工的 每天的打卡情况
                List<AttendsDTO> attendsList = attendPunchDTO.getAttendsList();
                if(CollectionUtil.isNotEmpty(attendsList)){
                    for (AttendsDTO attendsDTO : attendsList) {
                        //把每一天考勤状态添加到 集合中
                        list.add(attendsDTO.getAttendStatus());
                    }
                }
                //添加到返回值
                data.add(list);
            }
        }

        return data;
    }
    //封装表头
    private List<List<String>> header(List<AttendPunchDTO> attendPunchDTOList) {
        List<List<String>> headers = new ArrayList<>();
        headers.add(Arrays.asList("姓名"));
        headers.add(Arrays.asList("工号"));
        headers.add(Arrays.asList("部门"));
        headers.add(Arrays.asList("职位"));

        if(CollectionUtil.isNotEmpty(attendPunchDTOList)){
            AttendPunchDTO attendPunchDTO = attendPunchDTOList.get(0);
            List<AttendsDTO> attendsList = attendPunchDTO.getAttendsList();
            if(CollectionUtil.isNotEmpty(attendsList)){
                for (AttendsDTO attendsDTO : attendsList) {
                    String attendDate = attendsDTO.getAttendDate();
                    headers.add(Arrays.asList(attendDate));
                }

            }
        }
        return headers;
    }

    static class MyColumnStyle extends AbstractColumnWidthStyleStrategy {
        protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList,
                                      Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
            //改变 单元格 宽度
            writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), 4500);
        }
    }

    private String handleAttendStatus(List<AttendPunch> punches) {
        String amAttendStatus = "缺卡";//上午 - 考勤
        String pmAttendStatus = "缺卡";//下午 - 考勤

        if(CollectionUtil.isNotEmpty(punches)){
            for (AttendPunch punch : punches) {
                if(punch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_MORNING.value()){
                    amAttendStatus = this.getPushDesc(punch.getMorningPunchType());
                }else if (punch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_AFTERNOON.value()){
                    pmAttendStatus = this.getPushDesc(punch.getAfternoonPunchType());
                }
            }
        }

        return amAttendStatus+"/"+pmAttendStatus;
    }

    private String getPushDesc(Integer morningPunchType) {
        String desc = "";
        switch (morningPunchType){
            case 1:
                desc = AttendEnums.PUNCH_TYPE_OK.toString();
                break;
            case 2:
                desc = AttendEnums.PUNCH_TYPE_LATE.toString();
                break;
            case 3:
                desc = AttendEnums.PUNCH_TYPE_EARLY.toString();
                break;
            case 4:
                desc = AttendEnums.PUNCH_TYPE_STAYALAY.toString();
                break;
            case 5:
                desc = AttendEnums.PUNCH_TYPE_NOT_WORK_DAY.toString();
                break;
            default:
                break;
        }
        return desc;
    }

    /**
     * 1. 查询打卡记录，如果没有查询到，无论何时打卡都是有效的
     * 2. 查询到了
     *  如果是上午打卡， 则无效
     *  如果是下午打卡， 以最后一次打卡记录为准, 之前打卡记录无效
     * @param attendPunch
     * @return
     */
    private boolean validatePunch(AttendPunch attendPunch) {
        LambdaQueryWrapper<AttendPunch> punchQw = new LambdaQueryWrapper<>();

        punchQw.eq(AttendPunch::getPunchOnOffWork, attendPunch.getPunchOnOffWork());
        punchQw.eq(AttendPunch::getCompanyId, CurrentUserHolder.get().getCompanyId());
        punchQw.eq(AttendPunch::getCompanyUserId, CurrentUserHolder.get().getCompanyUserId());
        punchQw.eq(AttendPunch::getPunchDateStr , attendPunch.getPunchDateStr());

        Integer count = attendPunchMapper.selectCount(punchQw);

        if(count == 0){
            attendPunch.setEffectiveValid(true);
        }else{
            //查询到了打卡记录
            if(attendPunch.getPunchOnOffWork() == 1){
                //如果是上午打卡， 并且查询到 已经打卡， 则打卡无效
                attendPunch.setEffectiveValid(false);
            }else{
                //查询到了打卡， 并且是下午下午， 只要是下午打卡，无论 什么时候打卡，都有效
                attendPunch.setEffectiveValid(true);
                //修改下午之前的打卡为无效打卡
                AttendPunch updateAttendPunch = new AttendPunch();
                updateAttendPunch.setEffectiveValid(false);
                attendPunchMapper.update(updateAttendPunch, punchQw);

            }


        }

        return attendPunch.getEffectiveValid();
    }
}
